using System;
using System.Diagnostics;
using System.Threading;
using System.IO;

namespace kerberosLibs
{
	/// <summary>
	/// Simple process that runs in a seperate thread -- uses delgates to let others know something has happened!
	/// </summary>
	public class ProcessWatcher
	{
		private Process _proc = null;

		/// <summary>
		/// Setup for future running. Will stash the exe and args.
		/// </summary>
		/// <param name="executable"></param>
		/// <param name="args"></param>
		public ProcessWatcher(FileInfo executable, string args)
		{
			_proc = new Process ();

			_proc.StartInfo.Arguments = args;
			_proc.StartInfo.FileName = executable.FullName;

			_proc.StartInfo.RedirectStandardError = true;
			_proc.StartInfo.RedirectStandardOutput = true;
			_proc.StartInfo.RedirectStandardInput = true;

			_proc.StartInfo.UseShellExecute = false;

			_proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
			_proc.StartInfo.CreateNoWindow = true;

			_proc.EnableRaisingEvents = true;

			_proc.Exited +=new EventHandler(_proc_Exited);
		}

		public string WorkingDirectory
		{
			get {return _proc.StartInfo.WorkingDirectory;}
			set {_proc.StartInfo.WorkingDirectory = value;}
		}

		/// <summary>
		/// Return the executable, full path, that we are running.
		/// </summary>
		public string ExeLocation
		{
			get {return _proc.StartInfo.FileName;}
		}

		/// <summary>
		/// Add this directory to the environment path.
		/// </summary>
		/// <param name="path"></param>
		public void AddToPath (string path)
		{
			_proc.StartInfo.EnvironmentVariables["PATH"] += ";" + path;
		}

		/// <summary>
		/// Return the arguments we want to look at.
		/// </summary>
		public string ExeArguments 
		{
			get {return _proc.StartInfo.Arguments;}
		}

		private Thread _outReaderThread;
		private Thread _errReaderThread;

		/// <summary>
		/// The standard input stream for the process.
		/// </summary>
		public System.IO.StreamWriter StdInput
		{
			get {return _proc.StandardInput;}
		}

		/// <summary>
		/// Start the process running
		/// </summary>
		public void Start ()
		{
			_proc.Start();
			_outReaderThread = new Thread (new ThreadStart (this.readOutput));
			_errReaderThread = new Thread (new ThreadStart (this.readError));

			_outReaderThread.Start();
			_errReaderThread.Start();
		}

		/// <summary>
		/// Send a kill and terminate the process!
		/// </summary>
		public void Kill ()
		{
			if (!_proc.HasExited) 
			{
				_proc.Kill();
			}
		}

		/// <summary>
		/// Read the output stream
		/// </summary>
		private void readOutput()
		{
			readTillDone (_proc.StandardOutput);
			_proc.StandardOutput.Close();
		}

		/// <summary>
		/// Read the error stream.
		/// </summary>
		private void readError()
		{
			readTillDone (_proc.StandardError);
			_proc.StandardError.Close();
		}

		/// <summary>
		/// Callback when a line of output appears on either std out or std err.
		/// </summary>
		public delegate void ProcessOutputLine (string line);

		public event ProcessOutputLine OnProcessOutputLine;

		/// <summary>
		/// Read an input stream until we are done!
		/// </summary>
		/// <param name="reader"></param>
		private void readTillDone (System.IO.StreamReader reader)
		{
			string line;
			do 
			{
				line = reader.ReadLine ();
				if (OnProcessOutputLine != null) 
				{
					OnProcessOutputLine (line);
				}
			} while (line != null);
		}

		/// <summary>
		/// Call this when you have no more input to send to the running process.
		/// </summary>
		public void NoFurtherInput ()
		{
			_proc.StandardInput.Close();
		}

		public delegate void ProcessExit ();
		public event ProcessExit OnProcessExit;

		/// <summary>
		/// Called when the process exits.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void _proc_Exited(object sender, EventArgs e)
		{
			if (OnProcessExit != null) 
			{
				OnProcessExit ();
			}
		}

		/// <summary>
		/// Wait until the progress is looking for idle events.
		/// </summary>
		public void WaitTillIdle ()
		{
			_proc.WaitForInputIdle();
		}
	}
}
